<?php
class Content_model extends Model {
	function Content_model()
	{
		parent::Model();
	}
	
	function load_content($id, $bricabox_id)
	{
		$query = $this->db->getwhere('contents', array('id' => $id, 'bricabox_id' => $bricabox_id), 1); // limit 1
		if ($query->num_rows() == 1)
		{
			return $query->first_row();
		}
		else
		{
			show_404();
		}
	}
	
	function load_all($bricabox_id)
	{
		$query = $this->db->getwhere('contents', array('bricabox_id' => $bricabox_id));
		return $query->num_rows() ? $query : false;
	}
	
	function load_all_flagged($bricabox_id)
	{
		$query = $this->db->query("SELECT content_flags.*,contents.*,COUNT(content_flags.id) AS flag_count FROM content_flags
			INNER JOIN contents ON contents.id = content_flags.content_id
			WHERE contents.bricabox_id = ?
			GROUP BY content_flags.content_id",
			$bricabox_id);
		return $query->num_rows() ? $query : false;
	}
	
	function load_all_filtered($bricabox_id, $order_by = null, $content_type_id = null, $limit = null, $ctgy_id = null, $tags = null, $search = null, $upcoming_only = false)
	{
		// defaults
		if (!$order_by) $order_by = 'contents.created_at desc';
		
		$params = array(
			$bricabox_id
		);
		
		// content type filtering
		$content_type_where = "";
		if ($content_type_id)
		{
			$content_type_where = " and contents.content_type_id = ?";
			$params[] 			= $content_type_id;
		}
		
		// limit
		$limit_sql = "";
		if ($limit)
		{
			$limit 		= intval($limit);
			$limit_sql 	= "limit {$limit}";
		}
		
		// category
		$ctgy_sql = "";
		if ($ctgy_id)
		{
			$ctgy_sql = " and category_id = ?";
			$params[] = $ctgy_id;
		}
		
		// search
		$search_sql = "";
		if ($search)
		{
			$search_sql = " and (field1_value like ? or search_field2 like ?)";
			$params[]	= "%{$search}%";
			$params[]	= "%{$search}%";
		}
		
		// only upcoming entries
		$upcoming_sql = "";
		if ($upcoming_only === true)
		{
			$upcoming_sql = " and data_datetime > NOW()";
		}
		
		$query = $this->db->query("select contents.* from contents
				where contents.bricabox_id = ?
					{$content_type_where}
					{$ctgy_sql}
					{$search_sql}
					{$upcoming_sql}
				order by {$order_by}
				{$limit_sql}", $params);
		$_resultset = $query->num_rows() ? $query : false;
			
		// now for the tricky stuff; filter by tags
		if (count($tags) && $_resultset !== false)
		{
			$tag_ids = array();
			
			// get ids for each tag
			foreach ($tags as $tag)
			{
				$query = $this->db->query('select id from tags where name = ? limit 1', $tag);
				if ($query->num_rows() == 1)
					$tag_ids[$query->first_row()->id] = $tag;
			}
			
			// force preloading of results in order to popular $_resultset->result_object
			foreach ($_resultset->result() as $_r) {}
			foreach ($_resultset->result_object as $idx => $obj)
			{
				// get all tag-content join records for this content
				$query = $this->db->query('select tag_id from contents_tags
					where content_id = ?', array($obj->id));
				
				// any tag id from the filter list is NOT present in this content's tag id set, remove the content
				$remove 		= false;
				$content_tags 	= array();
				foreach ($query->result() as $tag_id)
					$content_tags[$tag_id->tag_id] = $tag_id->tag_id;
					
				foreach ($tag_ids as $id => $name)
					if (!in_array($id, $content_tags))
						$remove = true;
				
				if ($remove)
					unset($_resultset->result_object[$idx]);
			}
			// if (result_object is empty, we've removed every single content [none matched]), so set an alert
			if (count($_resultset->result_object) == 0)
			{
				foreach ($tag_ids as $id => $name)
					$tag_ids[$id] = '<strong>'.$name.'</strong>';
				$tag_string = join(' and ', array_values($tag_ids));
				$this->flashy->save_flash('notice', "No entries were found tagged with {$tag_string}, so here are all entries in this BricaBox.");
			}
		}
		return $_resultset;
	}
	
	// functions nearly the same as the rails incarnation of the overloaded Content.field2_value method
	// note: we don't worry about the file upload anymore...
	function content_data($content, $geocodable = false, $for_entry_list = false)
	{
		$CI =& get_instance();
		$CI->load->helper('date');
		
		$data = array();
		$type = $this->get_content_type($content->content_type_id);
		
		if (!$geocodable && $type->input_use_url)
		{		
			$data[] = "<p class='link'>";
			$data[] = $content->data_url;
			$data[] = "</p>";
		}
			
		if ($type->input_use_address)
		{
			$address = array();
			
			if (!$for_entry_list)
			{
				$address[] = $content->data_addr_str1;

				if (!$geocodable)
				{
					$address[] = $content->data_addr_str2;
				}
			}
			else
			{
				if (strlen($content->data_addr_str2))
				{
					$address[] = $content->data_addr_str1.' | '.$content->data_addr_str2;
				}
				else
				{
					$address[] = $content->data_addr_str1;
				}
			}
				
			if ($geocodable)
			{
				// one per line
				$address[] = $content->data_addr_city;
				$address[] = $content->data_addr_state;
				$address[] = $content->data_addr_postal;
				$address[] = $content->data_addr_country;
			}
			else
			{
				// city, state, zip
				// country
				$_csz[] = $content->data_addr_city;
				$_csz[] = $content->data_addr_state;
				$_csz[] = $content->data_addr_postal;
				
				if ($for_entry_list)
					$_csz[] = $content->data_addr_country;
				
				foreach ($_csz as $i=>$v)
					if (!strlen($_csz[$i]))
						unset($_csz[$i]);
						
				$address[] = join(', ', $_csz);
				
				if (!$for_entry_list)
					$address[] = $content->data_addr_country;
			}
			
			
			// unset empty elements
			foreach ($address as $k => $v)
				if (!strlen($v))
					unset($address[$k]);
			
			$data[] = "<p class='address'>";
			$data[] = join(($geocodable ? ", " : "\n"), $address);
			$data[] = "</p>";
		}
		
		if (!$geocodable && $type->input_use_subtitle)
		{
			$data[] = "<p class='description'>";
			$data[] = $for_entry_list ? character_limiter($content->data_subtitle, 99) : $content->data_subtitle;
			$data[] = "</p>";
		}
			
		if (!$geocodable && $type->input_use_datetime && $content->data_datetime)
		{
			$data[] = "<p class='date'>Posted on: ".$this->format_datetime($content->data_datetime)."</p>";
		}
		
		if (!$geocodable && $for_entry_list && $content->data_file_upload)
		{
			$data[] = "<p class='attachement'>";
			$data[] = '<a style="border: none;" href="'
				.url_for_attachment($content->data_file_upload, $content->id)
				.'"><img style="vertical-align: middle;" src="/assets/images/icons/attach.png" alt="Attachment" />'
				.character_limiter($content->data_file_upload, 35)
				.'</a>';
			$data[] = "</p>";
		}
			
		// unset empty elements
		foreach ($data as $k => $v)
			if (!strlen($v))
				unset($data[$k]);
			
		return join(($geocodable ? ", " : "\n"), $data);
	}
	
	function format_datetime($datetime)
	{
		$stamp = strtotime($datetime);
		
		// if there's clearly no time attached, drop it
		if (date('His', $stamp) == "000000")
		{
			return date('n/j/Y', $stamp);
		}
		else
		{
			return date('n/j/Y \a\t g:ia', $stamp);
		}
	}
	
	function geocodable_address($content)
	{
		return $this->content_data($content, true);
	}
	
	function get_content_type($type_id)
	{
		global $BBX;
		$has_mc = ($BBX['memcache'] === false) ? false : true;
		$mc_key = "content_type_{$type_id}";
		log_message('debug', "memcache: looking for content_type {$type_id}");
		if ($has_mc)
		{
			// check for cached version
			$mc_ct = $BBX['memcache']->get($mc_key);
			if ($mc_ct !== false)
			{
				// available
				log_message('debug', "memcache: content_type {$type_id} found");
				return $mc_ct;
			}
			else
			{
				// not available
				log_message('debug', "memcache: content_type {$type_id} not found");
				$ct = $this->_get_content_type_from_db($type_id);
				$BBX['memcache']->set($mc_key, $ct, MEMCACHE_COMPRESSED, 30); // 30 sec TTL
				return $ct;
			}
		}
		else
		{
			// no mc
			log_message('debug', "no memcache");
			return $this->_get_content_type_from_db($type_id);
		}
	}
	
	function _get_content_type_from_db($type_id)
	{
		$query = $this->db->getwhere('content_types', array('id' => $type_id), 1);
		if ($query->num_rows() == 1)
			return $query->first_row();
	}
	
	function is_content_type_empty($type_id_or_type)
	{
		if (is_numeric($type_id_or_type))
		{
			$type_id = $type_id_or_type;
			$_type	 = $this->get_content_type($type_id);
		}
		else
			$_type = $type_id_or_type;
			
		if (!$_type->input_use_file_upload && !$_type->input_use_url && !$_type->input_use_address && !$_type->input_use_subtitle && !$_type->input_use_datetime)
			return true;
	}
	
	// load content by $id first, then get the content type data
	function get_content_type_with_load($id, $bricabox_id)
	{
		$content = $this->load_content($id, $bricabox_id);
		$type	 = $this->get_content_type($content->content_type_id);
		return $type;
	}
	
	// loads a content type by name where the load is scoped to $bricabox_id
	function get_content_type_by_name($name, $bricabox_id)
	{
		// names with spaces end up passed thru with underscores; fix it
		$name = str_replace('_', ' ', $name);
		$query = $this->db->getwhere('content_types', array('name' => $name, 'bricabox_id' => $bricabox_id), 1);
		if ($query->num_rows() == 1)
			return $query->first_row();
	}
	
	// returns a boolean indicating whether the given content type can be deleted
	// true if both are true:
	// 1. it has no entries assigned to it
	// 2. it is not the only content type
	function content_type_can_be_deleted($type_id, $bricabox_id)
	{
		// only one?
		$count_query = $this->db->query('select count(*) as count_all from content_types where bricabox_id = ?', array($bricabox_id));
		if ($count_query->first_row()->count_all == 1)
			return false;
		else
		{
			// any content?
			$params		   = array($bricabox_id, $type_id);
			$content_query = $this->db->query('select count(*) as count_all from contents where bricabox_id = ? and content_type_id = ?', $params);
			if ($content_query->first_row()->count_all > 0)
				return false;
			else
			{
				// okay to delete
				return true;
			}
		}
	}
	
	function delete_content_type($type_id)
	{
		$this->db->where('id', $type_id);
		return $this->db->delete('content_types');
	}
	
	// content is treated as already existing if:
	// 1. field1_value matches
	// 2. search_field2 matches
	//
	// if the content *does* exist, return it
	function content_exists($bricabox_id, $params)
	{
		$ret = false;
		
		// create a temporary record
		$params['bricabox_id'] = $bricabox_id;
		$this->db->insert('contents', $params);
		$_temp_id = $this->db->insert_id();
		log_message('debug', "[content exists] temp content record created at $_temp_id");
		
		// get the temp record as a result object
		$temp_query 	= $this->db->getwhere('contents', array('id' => $_temp_id, 'bricabox_id' => $bricabox_id), 1); // limit 1
		$_temp_content 	= $temp_query->first_row();
		
		// check the db for exact matches
		$where = array(
			'bricabox_id' 	=> $bricabox_id,
			'field1_value' 	=> $params['field1_value'],
			'search_field2' => $this->content_data($_temp_content)
		);
		$match_query = $this->db->query('select id from contents where bricabox_id = ? and field1_value = ? and search_field2 = ? limit 1', $where);
		if ($match_query->num_rows() == 1)
		{
			// match found
			log_message('debug', "[content exists] match(es) found");
			$ret = $match_query->first_row()->id;
		}
		else
		{
			// no matches found
			log_message('debug', "[content exists] no matches");
			$ret = false;
		}
		
		// clean up the temporary record
		$this->db->query('delete from contents where id = ? and bricabox_id = ?', array($_temp_id, $bricabox_id));
		log_message('debug', "[content exists] removing temp record $_temp_id");
		
		return $ret;
	}
	
	// todo:
	// - encapsulate same content sanity checks
	// - old after_save() -> clear geocode stuff
	function save_content($bricabox_id, $type_id, $params, $new_record = false)
	{
		global $BBX;
		
		$result = false;
		
		// is field1 (title) empty?
		if (!isset($params['field1_value']) || !strlen($params['field1_value']))
			return false;
		
		// CI stuff
		$CI =& get_instance();
		$CI->load->helper('url');
		
		// generate a slug and push into $params
		$slug 			= url_title($params['field1_value'], 'underscore');
		$params['slug'] = $slug;
		
		// normalize the url, if given
		if (isset($params['data_url']))
			$params['data_url'] = prep_url($params['data_url']);
			
		$params['bricabox_id'] = $bricabox_id;	
		
		// build a SQL datetime from the passed in params
		if (isset($params['data_datetime']) && is_array($params['data_datetime']))
		{
			$dt = $params['data_datetime'];
			
			// if PM, add 12 to hour
			if (!empty($dt['hour']))
				if ($dt['ampm'] == 1)
					$dt['hour'] += 12;
			
			$mkt = mktime((int)@$dt['hour'], (int)@$dt['min'], 0, (int)@$dt['month'], (int)@$dt['day'], (int)@$dt['year']);
			$params['data_datetime'] = date('Y-m-d H:i:s', $mkt);
		}
		else
		{
			unset($params['data_datetime']);
		}
		
		if ($new_record)
		{
			// timestamps
			$params['created_at'] = date('Y-m-d H:i:s', time());
			$params['updated_at'] = date('Y-m-d H:i:s', time());

			// user id
			$params['user_id'] = $CI->userland->current_user()->id;

			// content type id
			$params['content_type_id'] = $type_id;
			
			// create
			$this->db->insert('contents', $params);
			
			// load the content, get its id, update a few key attrs and return it
			$content_id = $this->db->insert_id();
			$query 		= $this->db->getwhere('contents', array('id' => $content_id), 1); // limit 1
			$_content	= $query->first_row();
		}
		else
		{
			// update existing record; can the current user edit it?
			$content_id	= $params['content_id'];
			unset($params['content_id']);
			
			// todo: sanity check: does the current user have permission to update?
			
			$params['updated_at'] = date('Y-m-d H:i:s', time());
			unset($params['user_id']);
			
			$params['clear_geocode'] = 1;
			$params['geo_lat']		 = null;
			$params['geo_lon']		 = null;
			unset($params['data_file_upload']);
			
			$this->db->where('id', $content_id);
			$this->db->update('contents', $params);
			
			// re-load the content
			$query 		= $this->db->getwhere('contents', array('id' => $content_id), 1); // limit 1
			$_content	= $query->first_row();
		}
		
		//
		// regardless of create or save, update the two key content data/search fields and handle entry avatar and file attachment stuff
		//
		
		// field2_value and search_field2, original filename
		$_data 					 = $this->content_data($_content);
		$params 				 = array();
		$params['field2_value']  = $_data;
		$params['search_field2'] = $_data;
		$params['content_image'] = $_FILES['content']['name']['content_image'];
		
		// this may not always be set depending on content type
		if (isset($_FILES['content']['name']['data_file_upload']) && strlen($_FILES['content']['name']['data_file_upload']))
			$params['data_file_upload'] = @$_FILES['content']['name']['data_file_upload'];
		
		// avatar
		$this->upload_avatar($content_id,
							 $_FILES['content']['tmp_name']['content_image'],
							 $_FILES['content']['name']['content_image']);
		
		// file attachment
		if (isset($_FILES['content']['tmp_name']['data_file_upload']))
			$this->upload_attachment($content_id,
									 $_FILES['content']['tmp_name']['data_file_upload'],
									 $_FILES['content']['name']['data_file_upload']);
		
		// save our entry!
		$this->db->where('id', $content_id);
		$this->db->update('contents', $params);
		
		$result = $_content;
		
		return $result;
	}
	
	// destroy_content() does NOT check if the caller has permission to invoke this method
	function destroy_content($content_id)
	{
		$this->db->query('delete from contents where id = ?', array($content_id));
		return $this->db->affected_rows() ? true : false;
	}
	
	function get_mimetype_for($filename, $mimes)
	{
		$base = basename($filename);
		if (!preg_match('/\.([^\.].*)$/', $base, $matches))
			return 'text/plain'; // good default?
		else
		{
			if (isset($mimes[$matches[1]]))
			{
				if (is_array($mimes[$matches[1]]))
					return $mimes[$matches[1]][0]; // just return the first element; is this bad?
				else
					return $mimes[$matches[1]];
			}
		}
	}
	
	// uploads a file attachment to s3
	function upload_attachment($content_id, $file, $filename) // $file is the tmpfile, $filename is the original filename
	{
		if (!strlen($file) || !strlen($filename))
			return false;
		
		include(APPPATH.'config/mimes'.EXT);
		$_mimes = $mimes;
		
		$mimetype = $this->get_mimetype_for($filename, $_mimes);
		
		log_message('debug', "upload_attachment: uploading for {$content_id} with {$filename} as {$mimetype}");
		
		$hash = md5($filename);
		$ext  = end(explode('.', $filename));
		$filename = "{$hash}.{$ext}";
		
		$s3 	 = new s3();
		$s3_name = "attach_{$content_id}_{$filename}";
		
		log_message('debug', "upload_attachment: about to POST to s3 with $s3_name");
		$response = $s3->putObject($s3_name, file_get_contents($file), s3_bucket_url(), "public-read", $mimetype);
	}
	
	// creates thumbs and uploads 'em to s3
	// might as well make this extensible and handle user avatars, too    VVVVVVV
	function upload_avatar($content_id, $file, $filename, $avatar_type = 'content') // $file is the tmpfile, $filename is the original filename
	{
		if (!strlen($file) || !strlen($filename))
			return false;
		
		include(APPPATH.'config/mimes'.EXT);
		$_mimes = $mimes;
		
		$mimetype = $this->get_mimetype_for($filename, $_mimes);
		
		log_message('debug', "upload_avatar: uploading for {$content_id} with {$filename} as {$mimetype}");
		
		$s3 = new s3();
		
		$s3_name = $this->_s3_name_for_avatar($filename, $content_id, $avatar_type, 'original', APP_ENV);
		log_message('debug', "upload_avatar: about to POST to s3 with $s3_name");
		$response = $s3->putObject($s3_name, file_get_contents($file), s3_bucket_url(), "public-read", $mimetype);
		
		// create thumbs
		$this->load->library('image_lib');
		foreach (array('small' => 25, 'medium' => 50, 'large' => 100) as $size => $dim)
		{
			$imgconfig['create_thumb'] 	 = true;
			$imgconfig['maintain_ratio'] = false;
			$imgconfig['source_image'] = $file;
			$imgconfig['width']		   = $dim;
			$imgconfig['height']	   = $dim;
			$imgconfig['new_image']	   = BASEPATH.'../image_output/'.$filename;
			$imgconfig['thumb_marker'] = "";
			
			$this->image_lib->initialize($imgconfig);
			$this->image_lib->resize();
			
			log_message('debug', 'upload_avatar: '.print_r($imgconfig, true));
			log_message('debug', 'upload_avatar: '.$this->image_lib->display_errors());
			
			$thumbed_file 		= $file;
			$thumbed_filename 	= $imgconfig['new_image'];
			$s3_name 	  		= $this->_s3_name_for_avatar($filename, $content_id, $avatar_type, $size, APP_ENV);
			
			log_message('debug', "upload_avatar: about to POST to s3 with $s3_name at $size");
			
			$response = $s3->putObject($s3_name, file_get_contents($thumbed_filename), s3_bucket_url(), "public-read", $mimetype);
			
			$this->image_lib->clear();
		}
		return true;
	}
	
	function user_can_edit($user, $content_user_id)
	{
		global $BBX;
		
		$CI =& get_instance();
		$CI->load->model('Membership_model');
		
		if ($CI->userland->logged_in() && $CI->Membership_model->is_owner($BBX['bricabox']->id))
			return true;
		
		if ($user === false || !$user->id)
			return false;
			
		if ($content_user_id == $user->id)
			return true;
	}
	
	// $type can be user, content
	// $size is small, medium, large
	private function _s3_name_for_avatar($filename, $id, $type = 'user', $size = 'small', $env = 'development')
	{
		$name = "";
		$ext  = end(explode('.', $filename));
		if ($filename)
			$name = "{$type}_{$id}_{$size}_{$filename}";
		return md5($name).'.'.$ext;
	}

	private function _size_to_dimension($size = 'small')
	{
		$s = "";
		if ($size == 'small')
			$s = 25;
		if ($size == 'medium')
			$s = 50;
		if ($size == 'large')
			$s = 100;
		return intval($s);
	}
}

function s3_bucket_url($env = APP_ENV)
{
	if ($env == 'production')
		return 'static.bricabox.com';
	else
		return "bricabox_".$env;
}
?>